package redis

import (
	"context"
	"fmt"
	"gitee.com/lipore/plume/logger"
	redis "github.com/redis/go-redis/v9"
	"github.com/spf13/viper"
)

type Client interface {
	redis.UniversalClient
}

var client Client

type Options interface {
	Apply(config *Config)
}

type Config struct {
	Host     string
	Password string
	Database int
}

func NewConfig() *Config {
	return &Config{
		Host:     "127.0.0.1",
		Password: "",
		Database: 0,
	}
}

func (options *Config) Apply(config *Config) {
	config.Host = options.Host
	config.Password = options.Password
	config.Database = options.Database
}

func (options *Config) NewClient() *redis.Client {
	return redis.NewClient(&redis.Options{
		Addr:     options.Host,
		Password: options.Password,
		DB:       options.Database,
	})
}

type withAddress struct {
	host string
	port int
}

func (w *withAddress) Apply(config *Config) {
	if w.port == 0 {
		config.Host = fmt.Sprintf("%s:6379", w.host)
	} else {
		config.Host = fmt.Sprintf("%s:%d", w.host, w.port)
	}
}

func WithAddress(host string, port int) Options {
	return &withAddress{host: host, port: port}
}

type withPassword struct {
	password string
}

func (w *withPassword) Apply(config *Config) {
	config.Password = w.password
}

func WithPassword(password string) Options {
	return &withPassword{password: password}
}

type withDatabase struct {
	database int
}

func (w *withDatabase) Apply(config *Config) {
	config.Database = w.database
}

func WithDatabase(database int) Options {
	return &withDatabase{database: database}
}

func NewClient(opts ...Options) Client {
	config := NewConfig()
	for _, opt := range opts {
		opt.Apply(config)
	}
	return config.NewClient()
}

func ParseConfig(v *viper.Viper) []Options {
	var opts []Options
	if v == nil {
		logger.Warn("redis config not defined")
		return opts
	}
	host := v.GetString("host")
	if host == "" {
		host = "127.0.0.1"
		logger.Warn("host of redis is not set fallback to 127.0.0.1")
	}
	port := v.GetInt("port")
	if port == 0 {
		port = 6379
		logger.Warn("port of redis is not set fallback to 6379")
	}
	opts = append(opts, WithAddress(host, port))
	password := v.GetString("password")
	if password == "" {
		logger.Warn("password of redis is not set, fallback to use un-authed redis")
	}
	opts = append(opts, WithPassword(password))
	database := v.GetInt("database")
	opts = append(opts, WithDatabase(database))
	return opts
}

func CreateDefault(ctx context.Context, opts ...Options) {
	client = NewClient(opts...)
	_, err := client.Ping(ctx).Result()
	if err != nil {
		logger.Warn("can't connect to redis, please check config")
	}
}

func Default() Client {
	if client == nil {
		CreateDefault(context.Background())
	}
	return client
}
